//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
//
//	File		 :	ParseCLUSTAL.cpp
//	Contents	 :	Source file for class ParseMSF
//	Creation Date:	22/12/1999
//	Last Modified:	23/12/1999	lg
//	tabs width	 :	every 4 characters
//
//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888


#include <stdexcept>
#include <cassert>
#include <vector>
#include <string>
#include <sstream>
#include <string>
#include <algorithm>
#include <iostream>
#include "std_helper.h"
#include "parsephd.h"
#include "msfresource.h"
#ifdef _MFC_VER	// mfc specific code to follow
#include "../common/mfc_stdcpp.h"
#else
#include "../common/nonmfc_stdcpp.h"
#endif


//_______________________________________________________________________________________
//
//	Constructor
//
//		Just initializes variables
//		Last modified: lg 23/12/1999
//
//_______________________________________________________________________________________
CParsePHD::CParsePHD(CParseMessageCallback& callback, unsigned ln, bool ignoreLoop, bool merge, bool consolidate)
:		CParse(callback, IDS_ERR_INVALID_PHDFILE, IDS_ERR_PHDFILE),
		m_IgnoreLoop(ignoreLoop), m_MergePredictionLines(merge), m_Consolidate(consolidate)
	//("This is not a valid PHD file.\r\n");
	//"There is an error in the PHD file.\r\n"
{
	LineNumber = ln;
}


//_______________________________________________________________________________________
//
//	DoParseData
//
//		Parse PHD Stream into Sequences of secondary structure information
//		Output		: outputSequence
//		Last modified: lg 04/4/1999
//
//_______________________________________________________________________________________
void CParsePHD::DoParseData(CSequence& outputSequence)
{
	// add the extra data for phd secondary structure files
	outputSequence.ExtraNames.push_back("AA");
	outputSequence.ExtraNames.push_back("PHD");
	outputSequence.ExtraNames.push_back("Rel");
	outputSequence.ExtraNames.push_back("prH-");
	outputSequence.ExtraNames.push_back("prE-");
	outputSequence.ExtraNames.push_back("prL-");
	outputSequence.ExtraNames.push_back("subset: SUB");

	// only used when accessibility data is included
	outputSequence.ExtraNames.push_back("PHD sec");
	outputSequence.ExtraNames.push_back("Rel sec");
	outputSequence.ExtraNames.push_back("prH sec");
	outputSequence.ExtraNames.push_back("prE sec");
	outputSequence.ExtraNames.push_back("prL sec");
	outputSequence.ExtraNames.push_back("subset: SUB sec");
	outputSequence.ExtraNames.push_back("3st:    P_3 acc");
	outputSequence.ExtraNames.push_back("10st:   PHD acc");
	outputSequence.ExtraNames.push_back("Rel acc");
	outputSequence.ExtraNames.push_back("subset: SUB acc");
	
	outputSequence.ExtraData.assign(outputSequence.ExtraNames.size(), std::string());


	char* nextLine = Data_Stream ? Data_Stream - 1 : 0;

	// is true only when section of file containing phd is encountered
	bool foundPHDSection = false;

	// index of last matching name, -1 indicates null
	int last_NameMatch = 0;

	while (nextLine)
	{
		// get next line
		PrepareNextLine(nextLine, Data_Stream, LineNumber);


		// skip past initial whitespace
		SkipSpaces(Data_Stream);

		// discard blank line
		if (!*Data_Stream)
			continue;

	
		// first PHD line is "   AA   |"
		if (!foundPHDSection)
		{
			if (!*Data_Stream || Data_Stream[0] != 'A' || Data_Stream[1] != 'A')
				continue;
		}
		else
		{
			if (CheckMatchChars(Data_Stream, '_', 70))
				break;
		}

		// find |
		char* nameEnd = Data_Stream+1;
		while (*nameEnd && *nameEnd != '|')
			++nameEnd;
		if (!*nameEnd)
			continue;

		// find beginning of data
		char* dataStart = nameEnd + 1;
		if (!*dataStart)
			continue;

		foundPHDSection = true;

		// nameEnd point to |, have it point to one past name
		while (isspace(*(nameEnd-1)))
		{
			assert(nameEnd > Data_Stream);
			--nameEnd;
		}
		assert(nameEnd > Data_Stream);

		//check that the name matches one in the list		
		std::string name(Data_Stream, nameEnd-Data_Stream);
		if (-1 == MatchNames(outputSequence.ExtraNames, name, last_NameMatch))
			continue;


		// skip past next whitespace
		Data_Stream = dataStart;

		// the end is either another '|' or whitespace
		char* end = strchr(Data_Stream, '|');
		if (!end)
		{
			end = Data_Stream;
			SkipToSpace(end);
		}

		outputSequence.ExtraData[last_NameMatch].append(Data_Stream, end - Data_Stream);
	}
}

//_______________________________________________________________________________________
//
//	DoCheckData
//
//		Checks and tidies up sequences of secondary structure information
//		Output		: outputSequence
//		Last modified: lg 04/4/1999
//
//_______________________________________________________________________________________
bool CParsePHD::DoCheckData(CSequence& outputSequence)
{
	// prH-, prE-, prL- => prH, prE, prL
	// remove "-" from names
	for (unsigned i = 3; i <= 5; ++i)
		outputSequence.ExtraNames[i].erase(3);
	outputSequence.ExtraNames[6] = "SUB";
	outputSequence.ExtraNames[12] = "SUB sec";

/*	// warn if no PHD data in any of the categories
	for (int i = outputSequence.ExtraCount() - 1; i >= 0; --i)
		// "No data in any of the %d lines could be matched to the PHD category for "%s". "
		if (ParseWarn(outputSequence.ExtraData[i].length() == 0 && i != 5,
				IDS_ERR_PHD_SEQLENZERO, -(int)LineNumber,	outputSequence.ExtraNames[i].c_str()))
		return false;
*/
	// remove any empty categories
	for (int i = outputSequence.ExtraCount() - 1; i >= 0; --i)
		if(!outputSequence.ExtraData[i].length())
		{
			outputSequence.ExtraData.erase(outputSequence.ExtraData.begin() + i);
			outputSequence.ExtraNames.erase(outputSequence.ExtraNames.begin() + i);

		}

	// remove sec from names
	for (int i = outputSequence.ExtraCount() - 1; i >= 0; --i)
	{
		if (outputSequence.ExtraNames[i].length() == 7 &&
			!stricmp(outputSequence.ExtraNames[i].c_str()+3, " sec"))
		{
			std::string name(outputSequence.ExtraNames[i], 0, 3);
			if(std::find(outputSequence.ExtraNames.begin(),
						outputSequence.ExtraNames.end(), name) == outputSequence.ExtraNames.end())
				outputSequence.ExtraNames[i] = name;
		}
	}

	// "No PHD secondary structure data could be found. This can only be interpreted as an standard MSF file."
	if (outputSequence.FormatType == CSequence::ePHDSAF)
	{
		if ( ParseErr( !outputSequence.ExtraCount(), IDS_ERR_PHD_NODATASAF, LineNumber ) )
			return false;
	}
	else if ( ParseWarn( !outputSequence.ExtraCount(), IDS_ERR_PHD_NODATA, LineNumber ) )
		return false;

	// make sure all sequences are the same in length
	if (!outputSequence.CheckLengthsEq())
	{
		ParseErrLength(outputSequence);
		return false;
	}

/*	for (int i = outputSequence.ExtraCount() - 1; i >= 0; --i)
		// "There was an incorrect number of entries (a difference of %d) in the PHD data category \"%s\"."
		if (ParseErr(outputSequence.ExtraData[i].length() != outputSequence.Len,
				IDS_ERR_PHD_SEQLEN, -abs(outputSequence.ExtraData[i].length() - outputSequence.Len),
				outputSequence.ExtraNames[i].c_str()))
		return false;*/
	return true;

}

//_______________________________________________________________________________________
//
//	DoParse
//
//		Main entry point
//		Output		: outputSequence
//		Last modified: lg 04/4/1999
//
//_______________________________________________________________________________________
bool CParsePHD::DoParse(char* const data_Stream, CSequence& outputSequence)
{
	if (!outputSequence.Count())
		return false;

	// clear all variables
	Data_Stream = data_Stream;
	outputSequence.ExtraNames.clear();

	// if data_Stream == 0, DoParseData will do nothing
	//  and DoCheckData will scream

	DoParseData(outputSequence);
	if (!DoCheckData(outputSequence))
		return false;

	ArrangePredictionData(outputSequence);
	
//	outputSequence.FormatType = CSequence::ePHD;
	return true;

}


//_______________________________________________________________________________________
//
//	ArrangePredictionData()
//
//		Description	 : optionally merges and erases prediction data
//		Output		 : outputSequence
//		Last modified: lg 04/4/1999
//
//_______________________________________________________________________________________
void CParsePHD::ArrangePredictionData(CSequence& seq)
{
	std::vector<std::string>::iterator b = seq.ExtraNames.begin();
	std::vector<std::string>::iterator e = seq.ExtraNames.end();
	const int notFound = e - b;
	int lo = std::find(b, e, "PHD") - b;
	int hi = std::find(b, e, "SUB") - b;


	// remove loop characters
	if (m_IgnoreLoop)
	{
		// if low accuracy prediction exists, remove loop
		// (N.B. PHD uses L for Loop)
		if (lo != notFound)
			std::replace(seq.ExtraData[lo].begin(), seq.ExtraData[lo].end(), 'L', ' ');

		// if high accuracy prediction exists, remove loop
		if (hi != notFound)
			std::replace(seq.ExtraData[hi].begin(), seq.ExtraData[hi].end(), 'L', '.');

	}

	// merge low confidence prediction into the high confidence prediction line
	if (m_MergePredictionLines			 &&
		lo != notFound && hi != notFound )
	{
		std::string& strLo = seq.ExtraData[lo];
		std::string& strHi = seq.ExtraData[hi];
		for (unsigned i = 0; i < seq.Len; ++i)
			if (!isupper(strHi[i]) && isupper(strLo[i]))
				strHi[i] = (unsigned char)tolower(strLo[i]);
	}

	// remove all but high confidence line
	if (m_Consolidate)
	{
		if (hi != notFound)
		{
			seq.ExtraData.erase(seq.ExtraData.begin() + hi + 1, seq.ExtraData.end());
			seq.ExtraData.erase(seq.ExtraData.begin(), seq.ExtraData.begin() + hi);
			seq.ExtraNames.erase(seq.ExtraNames.begin() + hi + 1, seq.ExtraNames.end());
			seq.ExtraNames.erase(seq.ExtraNames.begin(), seq.ExtraNames.begin() + hi);
			seq.ExtraNames[0] = "PHD";
		}
		else
		{
			seq.ExtraData.clear();
			seq.ExtraNames.clear();
		}
	}
	else
	{
		if (m_IgnoreLoop)
		{
			int loop = std::find(b, e, "prL") - b;
			if (loop != notFound)
			{
				seq.ExtraData.erase(seq.ExtraData.begin() + loop);
				seq.ExtraNames.erase(seq.ExtraNames.begin() + loop);
			}
		}

	}
}	
